package io.gitee.binaryfox.firework;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * FireWork是线程间共用的类
 * - 16 byte顺序字符串[8byte时间戳 1byte回拨位 2byte ServiceId 5byte序列号]序列号不在下一秒重置
 * - 总体趋势递增
 * - 支持时间到 8888年左右
 * - 支持3844台相同微服务之间id唯一
 * - 支持时钟回拨61次,61次后时间还小于上次回拨时间时通过消费未来时间确保ID不重复
 * - 支持监听时钟回拨告警或者其他业务处理
 * - 性能在多线程的时候表现良好(10000/ms 多余snowflake 4000/ms)
 * - 通过调用系统自己实现存取接口来确保下次启动加载回拨位和回拨最大时间
 */
public class FireWork {

    private AtomicInteger seq = new AtomicInteger(0);

    private ThreadLocal<FireWorkBuilder> threadLocal = new ThreadLocal<>();
    //long防止溢出 并且一定保证正数
    long flag;
    int serviceId;
    //回拨ring 记录回拨信息
    long[] before;
    private FireWorkStepBackHandler handler;

    FireWork(int serviceId, FireWorkStepBackHandler handler) {
        this.serviceId = serviceId;
        if (handler != null) {
            before = handler.getStepBackTimeRecordArray(serviceId);
            flag = handler.getStep(serviceId);
        }
        if (before == null) {
            before = new long[FireWorkSubString.len()];
        }
    }

    /**
     * 线程内上报回拨位和回拨前的时间
     * 记录在一个环里面
     * 如果下次回拨位回到这个位置时,回拨时间还小于此值会生成一个偏移量用来消费未来时间
     * 确保ID不重复
     *
     * @param reportFlag
     * @param beforeMill
     */
    void reportStepBack(long reportFlag, long beforeMill) {
        synchronized (this) {
            if (this.flag < reportFlag) {
                this.flag = reportFlag;
                if (handler != null) {
                    handler.setStep(serviceId, this.flag);
                }
            }
            int index = (int) (reportFlag % FireWorkSubString.len());
            if (beforeMill > before[index]) {
                before[index] = beforeMill;
                if (handler != null) {
                    handler.setStepBackTimeRecordArray(serviceId, index, beforeMill);
                }
            }
        }
    }

    /**
     * 真正的组织数据在线程自己里面 这样可以确保无锁
     *
     * @return
     */
    private FireWorkBuilder getGenerator() {
        FireWorkBuilder fireWork = threadLocal.get();
        if (fireWork == null) {
            fireWork = new FireWorkBuilder(this);
            threadLocal.set(fireWork);
        }
        return fireWork;
    }

    public String nextId() {
        int sequence = seq.getAndAdd(1);
        return getGenerator().buildId(sequence);
    }


}
